/**
 * Copyright 2010 The Apache Software Foundation
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.wasp.jdbc.value;

import com.alibaba.wasp.DataType;
import com.alibaba.wasp.SQLErrorCode;
import com.alibaba.wasp.jdbc.JdbcException;
import com.alibaba.wasp.util.DateTimeUtils;
import com.alibaba.wasp.util.StringUtils;
import com.alibaba.wasp.util.Utils;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;

/**
 * This is the base class for all value classes. It provides conversion and
 * comparison methods.
 */
public abstract class Value {

  /**
   * The data type is unknown at this time.
   */
  public static final int UNKNOWN = -1;

  /**
   * The value type for NULL.
   */
  public static final int NULL = 0;

  /**
   * The value type for BOOLEAN values.
   */
  public static final int BOOLEAN = 1;

  /**
   * The value type for BYTE values.
   */
  public static final int BYTE = 2;

  /**
   * The value type for SHORT values.
   */
  public static final int SHORT = 3;

  /**
   * The value type for INT values.
   */
  public static final int INT = 4;

  /**
   * The value type for LONG values.
   */
  public static final int LONG = 5;

  /**
   * The value type for DECIMAL values.
   */
  public static final int DECIMAL = 6;

  /**
   * The value type for DOUBLE values.
   */
  public static final int DOUBLE = 7;

  /**
   * The value type for FLOAT values.
   */
  public static final int FLOAT = 8;

  /**
   * The value type for INT values.
   */
  public static final int TIME = 9;

  /**
   * The value type for DATE values.
   */
  public static final int DATE = 10;

  /**
   * The value type for TIMESTAMP values.
   */
  public static final int TIMESTAMP = 11;

  /**
   * The value type for BYTES values.
   */
  public static final int BYTES = 12;

  /**
   * The value type for STRING values.
   */
  public static final int STRING = 13;

  /**
   * The value type for case insensitive STRING values.
   */
  public static final int STRING_IGNORECASE = 14;

  /**
   * The value type for BLOB values.
   */
  public static final int BLOB = 15;

  /**
   * The value type for CLOB values.
   */
  public static final int CLOB = 16;

  /**
   * The value type for ARRAY values.
   */
  public static final int ARRAY = 17;

  /**
   * The value type for RESULT_SET values.
   */
  public static final int RESULT_SET = 18;
  /**
   * The value type for JAVA_OBJECT values.
   */
  public static final int JAVA_OBJECT = 19;

  /**
   * The value type for UUID values.
   */
  public static final int UUID = 20;

  /**
   * The value type for string values with a fixed size.
   */
  public static final int STRING_FIXED = 21;

  /**
   * The number of value types.
   */
  public static final int TYPE_COUNT = STRING_FIXED + 1;

  /**
   * Get the SQL expression for this value.
   * 
   * @return the SQL expression
   */
  public abstract String getSQL();

  /**
   * Get the value type.
   * 
   * @return the type
   */
  public abstract int getType();

  /**
   * Get the precision.
   * 
   * @return the precision
   */
  public abstract long getPrecision();

  /**
   * Get the value as a string.
   * 
   * @return the string
   */
  public abstract String getString();

  /**
   * Get the value as an object.
   * 
   * @return the object
   */
  public abstract Object getObject();

  /**
   * Set the value as a parameter in a prepared statement.
   * 
   * @param prep
   *          the prepared statement
   * @param parameterIndex
   *          the parameter index
   */
  public abstract void set(PreparedStatement prep, int parameterIndex)
      throws SQLException;

  public abstract int hashCode();

  /**
   * Check if the two values have the same hash code. No data conversion is
   * made; this method returns false if the other object is not of the same
   * class. For some values, compareTo may return 0 even if equals return false.
   * Example: ValueDecimal 0.0 and 0.00.
   * 
   * @param other
   *          the other value
   * @return true if they are equal
   */
  public abstract boolean equals(Object other);

  /**
   * Get the order of this value type.
   * 
   * @param type
   *          the value type
   * @return the order number
   */
  static int getOrder(int type) {
    switch (type) {
    case UNKNOWN:
      return 1;
    case NULL:
      return 2;
    case STRING:
      return 10;
    case CLOB:
      return 11;
    case STRING_FIXED:
      return 12;
    case STRING_IGNORECASE:
      return 13;
    case BOOLEAN:
      return 20;
    case BYTE:
      return 21;
    case SHORT:
      return 22;
    case INT:
      return 23;
    case LONG:
      return 24;
    case DECIMAL:
      return 25;
    case FLOAT:
      return 26;
    case DOUBLE:
      return 27;
    case TIME:
      return 30;
    case DATE:
      return 31;
    case TIMESTAMP:
      return 32;
    case BYTES:
      return 40;
    case BLOB:
      return 41;
    case UUID:
      return 42;
    case JAVA_OBJECT:
      return 43;
    case ARRAY:
      return 50;
    case RESULT_SET:
      return 51;
    default:
      throw JdbcException.throwInternalError("type:" + type);
    }
  }

  /**
   * Get the higher value order type of two value types. If values need to be
   * converted to match the other operands value type, the value with the lower
   * order is converted to the value with the higher order.
   * 
   * @param t1
   *          the first value type
   * @param t2
   *          the second value type
   * @return the higher value type of the two
   */
  public static int getHigherOrder(int t1, int t2) {
    if (t1 == Value.UNKNOWN || t2 == Value.UNKNOWN) {
      if (t1 == t2) {
        throw JdbcException.get(SQLErrorCode.UNKNOWN_DATA_TYPE_1, "?, ?");
      } else if (t1 == Value.NULL) {
        throw JdbcException.get(SQLErrorCode.UNKNOWN_DATA_TYPE_1, "NULL, ?");
      } else if (t2 == Value.NULL) {
        throw JdbcException.get(SQLErrorCode.UNKNOWN_DATA_TYPE_1, "?, NULL");
      }
    }
    if (t1 == t2) {
      return t1;
    }
    int o1 = getOrder(t1);
    int o2 = getOrder(t2);
    return o1 > o2 ? t1 : t2;
  }

  /**
   * Clear the value cache. Used for testing.
   */
  public static void clearCache() {
  }

  public Boolean getBoolean() {
    return ((ValueBoolean) convertTo(Value.BOOLEAN)).getBoolean();
  }

  public Date getDate() {
    return ((ValueDate) convertTo(Value.DATE)).getDate();
  }

  public Time getTime() {
    return ((ValueTime) convertTo(Value.TIME)).getTime();
  }

  public Timestamp getTimestamp() {
    return ((ValueTimestamp) convertTo(Value.TIMESTAMP)).getTimestamp();
  }

  public byte[] getBytes() {
    return ((ValueBytes) convertTo(Value.BYTES)).getBytes();
  }

  public byte[] getBytesNoCopy() {
    return ((ValueBytes) convertTo(Value.BYTES)).getBytesNoCopy();
  }

  public byte getByte() {
    return ((ValueByte) convertTo(Value.BYTE)).getByte();
  }

  public short getShort() {
    return ((ValueShort) convertTo(Value.SHORT)).getShort();
  }

  public BigDecimal getBigDecimal() {
    return ((ValueDecimal) convertTo(Value.DECIMAL)).getBigDecimal();
  }

  public double getDouble() {
    return ((ValueDouble) convertTo(Value.DOUBLE)).getDouble();
  }

  public float getFloat() {
    return ((ValueFloat) convertTo(Value.FLOAT)).getFloat();
  }

  public int getInt() {
    return ((ValueInt) convertTo(Value.INT)).getInt();
  }

  public long getLong() {
    return ((ValueLong) convertTo(Value.LONG)).getLong();
  }

  public InputStream getInputStream() {
    return new ByteArrayInputStream(getBytesNoCopy());
  }

  /**
   * Add a value and return the result.
   * 
   * 
   * 
   * @param v
   *          the value to add
   * @return the result
   */
  public Value add(Value v) {
    throw throwUnsupportedExceptionForType("+");
  }

  public int getSignum() {
    throw throwUnsupportedExceptionForType("SIGNUM");
  }

  /**
   * Return -value if this value support arithmetic operations.
   * 
   * @return the negative
   */
  public Value negate() {
    throw throwUnsupportedExceptionForType("NEG");
  }

  /**
   * Subtract a value and return the result.
   * 
   * @param v
   *          the value to subtract
   * @return the result
   */
  public Value subtract(Value v) {
    throw throwUnsupportedExceptionForType("-");
  }

  /**
   * Divide by a value and return the result.
   * 
   * @param v
   *          the value to divide by
   * @return the result
   */
  public Value divide(Value v) {
    throw throwUnsupportedExceptionForType("/");
  }

  /**
   * Multiply with a value and return the result.
   * 
   * @param v
   *          the value to multiply with
   * @return the result
   */
  public Value multiply(Value v) {
    throw throwUnsupportedExceptionForType("*");
  }

  /**
   * Take the modulus with a value and return the result.
   * 
   * @param v
   *          the value to take the modulus with
   * @return the result
   */
  public Value modulus(Value v) {
    throw throwUnsupportedExceptionForType("%");
  }

  /**
   * Compare a value to the specified type.
   * 
   * @param targetType
   *          the type of the returned value
   * @return the converted value
   */
  public Value convertTo(int targetType) {
    // converting NULL is done in ValueNull
    // converting BLOB to CLOB and vice versa is done in ValueLob
    if (getType() == targetType) {
      return this;
    }
    try {
      // decimal conversion
      switch (targetType) {
      case BOOLEAN: {
        switch (getType()) {
        case BYTE:
        case SHORT:
        case INT:
        case LONG:
        case DECIMAL:
        case DOUBLE:
        case FLOAT:
          return ValueBoolean.get(getSignum() != 0);
        case TIME:
        case DATE:
        case TIMESTAMP:
        case BYTES:
        case JAVA_OBJECT:
        case UUID:
          throw JdbcException.get(SQLErrorCode.DATA_CONVERSION_ERROR_1,
              getString());
        }
        break;
      }
      case BYTE: {
        switch (getType()) {
        case BOOLEAN:
          return ValueByte.get(getBoolean().booleanValue() ? (byte) 1
              : (byte) 0);
        case SHORT:
          return ValueByte.get(convertToByte(getShort()));
        case INT:
          return ValueByte.get(convertToByte(getInt()));
        case LONG:
          return ValueByte.get(convertToByte(getLong()));
        case DECIMAL:
          return ValueByte.get(convertToByte(convertToLong(getBigDecimal())));
        case DOUBLE:
          return ValueByte.get(convertToByte(convertToLong(getDouble())));
        case FLOAT:
          return ValueByte.get(convertToByte(convertToLong(getFloat())));
        case BYTES:
          return ValueByte.get((byte) Integer.parseInt(getString(), 16));
        }
        break;
      }
      case SHORT: {
        switch (getType()) {
        case BOOLEAN:
          return ValueShort.get(getBoolean().booleanValue() ? (short) 1
              : (short) 0);
        case BYTE:
          return ValueShort.get(getByte());
        case INT:
          return ValueShort.get(convertToShort(getInt()));
        case LONG:
          return ValueShort.get(convertToShort(getLong()));
        case DECIMAL:
          return ValueShort.get(convertToShort(convertToLong(getBigDecimal())));
        case DOUBLE:
          return ValueShort.get(convertToShort(convertToLong(getDouble())));
        case FLOAT:
          return ValueShort.get(convertToShort(convertToLong(getFloat())));
        case BYTES:
          return ValueShort.get((short) Integer.parseInt(getString(), 16));
        }
        break;
      }
      case INT: {
        switch (getType()) {
        case BOOLEAN:
          return ValueInt.get(getBoolean().booleanValue() ? 1 : 0);
        case BYTE:
          return ValueInt.get(getByte());
        case SHORT:
          return ValueInt.get(getShort());
        case LONG:
          return ValueInt.get(convertToInt(getLong()));
        case DECIMAL:
          return ValueInt.get(convertToInt(convertToLong(getBigDecimal())));
        case DOUBLE:
          return ValueInt.get(convertToInt(convertToLong(getDouble())));
        case FLOAT:
          return ValueInt.get(convertToInt(convertToLong(getFloat())));
        case BYTES:
          return ValueInt.get((int) Long.parseLong(getString(), 16));
        }
        break;
      }
      case LONG: {
        switch (getType()) {
        case BOOLEAN:
          return ValueLong.get(getBoolean().booleanValue() ? 1 : 0);
        case BYTE:
          return ValueLong.get(getByte());
        case SHORT:
          return ValueLong.get(getShort());
        case INT:
          return ValueLong.get(getInt());
        case DECIMAL:
          return ValueLong.get(convertToLong(getBigDecimal()));
        case DOUBLE:
          return ValueLong.get(convertToLong(getDouble()));
        case FLOAT:
          return ValueLong.get(convertToLong(getFloat()));
        case BYTES: {
          // parseLong doesn't work for ffffffffffffffff
          byte[] d = getBytes();
          if (d.length == 8) {
            return ValueLong.get(Utils.readLong(d, 0));
          }
          return ValueLong.get(Long.parseLong(getString(), 16));
        }
        }
        break;
      }
      case DECIMAL: {
        // convert to string is required for JDK 1.4
        switch (getType()) {
        case BOOLEAN:
          return ValueDecimal.get(BigDecimal.valueOf(getBoolean()
              .booleanValue() ? 1 : 0));
        case BYTE:
          return ValueDecimal.get(BigDecimal.valueOf(getByte()));
        case SHORT:
          return ValueDecimal.get(BigDecimal.valueOf(getShort()));
        case INT:
          return ValueDecimal.get(BigDecimal.valueOf(getInt()));
        case LONG:
          return ValueDecimal.get(BigDecimal.valueOf(getLong()));
        case DOUBLE: {
          double d = getDouble();
          if (Double.isInfinite(d) || Double.isNaN(d)) {
            throw JdbcException.get(SQLErrorCode.DATA_CONVERSION_ERROR_1, ""
                + d);
          }
          return ValueDecimal.get(BigDecimal.valueOf(d));
        }
        case FLOAT: {
          float f = getFloat();
          if (Float.isInfinite(f) || Float.isNaN(f)) {
            throw JdbcException.get(SQLErrorCode.DATA_CONVERSION_ERROR_1, ""
                + f);
          }
          return ValueDecimal.get(new BigDecimal(Float.toString(f)));
        }
        }
        break;
      }
      case DOUBLE: {
        switch (getType()) {
        case BOOLEAN:
          return ValueDouble.get(getBoolean().booleanValue() ? 1 : 0);
        case BYTE:
          return ValueDouble.get(getByte());
        case SHORT:
          return ValueDouble.get(getShort());
        case INT:
          return ValueDouble.get(getInt());
        case LONG:
          return ValueDouble.get(getLong());
        case DECIMAL:
          return ValueDouble.get(getBigDecimal().doubleValue());
        case FLOAT:
          return ValueDouble.get(getFloat());
        }
        break;
      }
      case FLOAT: {
        switch (getType()) {
        case BOOLEAN:
          return ValueFloat.get(getBoolean().booleanValue() ? 1 : 0);
        case BYTE:
          return ValueFloat.get(getByte());
        case SHORT:
          return ValueFloat.get(getShort());
        case INT:
          return ValueFloat.get(getInt());
        case LONG:
          return ValueFloat.get(getLong());
        case DECIMAL:
          return ValueFloat.get(getBigDecimal().floatValue());
        case DOUBLE:
          return ValueFloat.get((float) getDouble());
        }
        break;
      }
      case DATE: {
        switch (getType()) {
        case TIME:
          // because the time has set the date to 1970-01-01,
          // this will be the result
          return ValueDate.fromDateValue(DateTimeUtils.dateValue(1970, 1, 1));
        case TIMESTAMP:
          return ValueDate
              .fromDateValue(((ValueTimestamp) this).getDateValue());
        }
        break;
      }
      case TIME: {
        switch (getType()) {
        case DATE:
          // need to normalize the year, month and day
          // because a date has the time set to 0, the result will be 0
          return ValueTime.fromNanos(0);
        case TIMESTAMP:
          return ValueTime.fromNanos(((ValueTimestamp) this).getNanos());
        }
        break;
      }
      case TIMESTAMP: {
        switch (getType()) {
        case TIME:
          // return DateTimeUtils.normalizeTimestamp(0,
          // ((ValueTime) this).getNanos());
          return null;
        case DATE:
          return ValueTimestamp.fromDateValueAndNanos(
              ((ValueDate) this).getDateValue(), 0);
        }
        break;
      }
      case BYTES: {
        switch (getType()) {
        case JAVA_OBJECT:
        case BLOB:
          return ValueBytes.getNoCopy(getBytesNoCopy());
        case UUID:
          return ValueBytes.getNoCopy(getBytes());
        case BYTE:
          return ValueBytes.getNoCopy(new byte[]{getByte()});
        case SHORT: {
          int x = getShort();
          return ValueBytes.getNoCopy(new byte[]{(byte) (x >> 8), (byte) x});
        }
        case INT: {
          int x = getInt();
          return ValueBytes.getNoCopy(new byte[]{(byte) (x >> 24),
              (byte) (x >> 16), (byte) (x >> 8), (byte) x});
        }
        case LONG: {
          long x = getLong();
          return ValueBytes.getNoCopy(new byte[]{(byte) (x >> 56),
              (byte) (x >> 48), (byte) (x >> 40), (byte) (x >> 32),
              (byte) (x >> 24), (byte) (x >> 16), (byte) (x >> 8), (byte) x});
        }
        }
        break;
      }
      case JAVA_OBJECT:
      case BLOB:
      case UUID:
      }
      // conversion by parsing the string value
      String s = getString();
      switch (targetType) {
      case NULL:
        return ValueNull.INSTANCE;
      case BOOLEAN: {
        if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("t")
            || s.equalsIgnoreCase("yes") || s.equalsIgnoreCase("y")) {
          return ValueBoolean.get(true);
        } else if (s.equalsIgnoreCase("false") || s.equalsIgnoreCase("f")
            || s.equalsIgnoreCase("no") || s.equalsIgnoreCase("n")) {
          return ValueBoolean.get(false);
        } else {
          // convert to a number, and if it is not 0 then it is true
          return ValueBoolean.get(new BigDecimal(s).signum() != 0);
        }
      }
      case BYTE:
        return ValueByte.get(Byte.parseByte(s.trim()));
      case SHORT:
        return ValueShort.get(Short.parseShort(s.trim()));
      case INT:
        return ValueInt.get(Integer.parseInt(s.trim()));
      case LONG:
        return ValueLong.get(Long.parseLong(s.trim()));
      case DECIMAL:
        return ValueDecimal.get(new BigDecimal(s.trim()));
      case TIME:
        return ValueTime.parse(s.trim());
      case DATE:
        return ValueDate.parse(s.trim());
      case TIMESTAMP:
        return ValueTimestamp.parse(s.trim());
      case BYTES:
        return ValueBytes.getNoCopy(StringUtils.convertHexToBytes(s.trim()));
      case JAVA_OBJECT:
      case STRING:
        return ValueString.get(s);
      case STRING_IGNORECASE:
        return ValueStringIgnoreCase.get(s);
      case STRING_FIXED:
        return ValueStringFixed.get(s);
      case DOUBLE:
        return ValueDouble.get(Double.parseDouble(s.trim()));
      case FLOAT:
        return ValueFloat.get(Float.parseFloat(s.trim()));
      case CLOB:
      case BLOB:
      case ARRAY:
        return ValueArray.get(new Value[]{ValueString.get(s)});
      case RESULT_SET:
      case UUID:
      default:
        throw JdbcException.throwInternalError("type=" + targetType);
      }
    } catch (NumberFormatException e) {
      throw JdbcException.get(SQLErrorCode.DATA_CONVERSION_ERROR_1, e,
          getString());
    }
  }

  /**
   * Compare this value against another value given that the values are of the
   * same data type.
   * 
   * @param v
   *          the other value
   * @param mode
   *          the compare mode
   * @return 0 if both values are equal, -1 if the other value is smaller, and 1
   *         otherwise
   */
  public final int compareTypeSave(Value v, CompareMode mode) {
    if (this == v) {
      return 0;
    } else if (this == ValueNull.INSTANCE) {
      return -1;
    } else if (v == ValueNull.INSTANCE) {
      return 1;
    }
    return compareSecure(v, mode);
  }

  /**
   * Compare this value against another value using the specified compare mode.
   * 
   * @param v
   *          the other value
   * @param mode
   *          the compare mode
   * @return 0 if both values are equal, -1 if the other value is smaller, and 1
   *         otherwise
   */
  public final int compareTo(Value v, CompareMode mode) {
    if (this == v) {
      return 0;
    }
    if (this == ValueNull.INSTANCE) {
      return v == ValueNull.INSTANCE ? 0 : -1;
    } else if (v == ValueNull.INSTANCE) {
      return 1;
    }
    if (getType() == v.getType()) {
      return compareSecure(v, mode);
    }
    int t2 = Value.getHigherOrder(getType(), v.getType());
    return convertTo(t2).compareSecure(v.convertTo(t2), mode);
  }

  /**
   * Compare the value with another value of the same type.
   * 
   * @param v
   *          the other value
   * @param mode
   *          the compare mode
   * @return 0 if both values are equal, -1 if the other value is smaller, and 1
   *         otherwise
   */
  protected abstract int compareSecure(Value v, CompareMode mode);

  public int getScale() {
    return 0;
  }

  /**
   * Convert the scale.
   * 
   * @param onlyToSmallerScale
   *          if the scale should not reduced
   * @param targetScale
   *          the requested scale
   * @return the value
   */
  public Value convertScale(boolean onlyToSmallerScale, int targetScale) {
    return this;
  }

  /**
   * Convert the precision to the requested value. The precision of the returned
   * value may be somewhat larger than requested, because values with a fixed
   * precision are not truncated.
   * 
   * @param precision
   *          the new precision
   * @param force
   *          true if losing numeric precision is allowed
   * @return the new value
   */
  public Value convertPrecision(long precision, boolean force) {
    return this;
  }

  private static byte convertToByte(long x) {
    if (x > Byte.MAX_VALUE || x < Byte.MIN_VALUE) {
      throw JdbcException.get(SQLErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1,
          Long.toString(x));
    }
    return (byte) x;
  }

  private static short convertToShort(long x) {
    if (x > Short.MAX_VALUE || x < Short.MIN_VALUE) {
      throw JdbcException.get(SQLErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1,
          Long.toString(x));
    }
    return (short) x;
  }

  private static int convertToInt(long x) {
    if (x > Integer.MAX_VALUE || x < Integer.MIN_VALUE) {
      throw JdbcException.get(SQLErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1,
          Long.toString(x));
    }
    return (int) x;
  }

  private static long convertToLong(double x) {
    if (x > Long.MAX_VALUE || x < Long.MIN_VALUE) {
      throw JdbcException.get(SQLErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1,
          Double.toString(x));
    }
    return Math.round(x);
  }

  private static long convertToLong(BigDecimal x) {
    return 0;
  }

  /**
   * Check if this value is linked to a specific table. For values that are kept
   * fully in memory, this method returns false.
   * 
   * @return true if it is
   */
  public boolean isLinked() {
    return false;
  }

  /**
   * Mark any underlying resource as 'not linked to any table'. For values that
   * are kept fully in memory this method has no effect.
   */
  public void unlink() {
    // nothing to do
  }

  /**
   * Close the underlying resource, if any. For values that are kept fully in
   * memory this method has no effect.
   */
  public void close() {
    // nothing to do
  }

  /**
   * Check if the precision is smaller or equal than the given precision.
   * 
   * @param precision
   *          the maximum precision
   * @return true if the precision of this value is smaller or equal to the
   *         given precision
   */
  public boolean checkPrecision(long precision) {
    return getPrecision() <= precision;
  }

  /**
   * Get a medium size SQL expression for debugging or tracing. If the precision
   * is too large, only a subset of the value is returned.
   * 
   * @return the SQL expression
   */
  public String getTraceSQL() {
    return getSQL();
  }

  public String toString() {
    return getTraceSQL();
  }

  /**
   * Throw the exception that the feature is not support for the given data
   * type.
   * 
   * @param op
   *          the operation
   * @return never returns normally
   * @throws com.alibaba.wasp.jdbc.JdbcException
   *           the exception
   */
  protected JdbcException throwUnsupportedExceptionForType(String op) {
    throw JdbcException.getUnsupportedException(DataType.getDataType(getType())
        .name() + " " + op);
  }

  /**
   * Get the table (only for LOB object).
   * 
   * @return the table id
   */
  public int getTableId() {
    return 0;
  }

  /**
   * Get the byte array.
   * 
   * @return the byte array
   */
  public byte[] getSmall() {
    return null;
  }

  /**
   * Copy this value to a temporary file if necessary.
   * 
   * @return the new value
   */
  public Value copyToTemp() {
    return this;
  }

  public ResultSet getResultSet() {
    return null;
  }

  public static Value toValue(byte[] values, DataType dataType) {
    switch (dataType) {
    case INT32:
      if(values == null || values.length == 0) {
        return ValueInt.ZERO;
      }
      return ValueInt.get(Bytes.toInt(values));
    case INT64:
      if(values == null || values.length == 0) {
        return ValueLong.ZERO;
      } else if (values.length == 4) {
        return ValueInt.get(Bytes.toInt(values));
      } else {
        return ValueLong.get(Bytes.toLong(values));
      }

    case DOUBLE:
      if(values == null || values.length == 0) {
        return ValueDouble.ZERO;
      }
      return ValueDouble.get(Bytes.toDouble(values));
    case FLOAT:
      if(values == null || values.length == 0) {
        return ValueFloat.ZERO;
      }
      return ValueFloat.get(Bytes.toFloat(values));
    case STRING:
      return ValueString.get(Bytes.toString(values));
    case DATETIME:
      if(values == null || values.length == 0) {
        return ValueNull.INSTANCE;
      }
      Long ret = Bytes.toLong(values);
      return ValueTimestamp.get(new Timestamp(ret));
    case PROTOBUF:
      // TODO
      return ValueProtobufObject.get(values);
    case UNKNOW:
      return ValueBytes.get(values);
    default:
      throw JdbcException.get(SQLErrorCode.PARSE_ERROR_2,
          "type=" + dataType.name());
    }

  }

  /**
   * A "binary large object".
   */
  public interface ValueClob {
    // this is a marker interface
  }

  /**
   * A "character large object".
   */
  public interface ValueBlob {
    // this is a marker interface
  }

}
